﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Reflection;
using System.Diagnostics;
using System.Windows.Media.Animation;

namespace Sela.NavigationLight.WPF4
{
	public class NavigationHelper : INavigationHelper
	{
		private static NavigationHelper _default = null;
		public static NavigationHelper Default
		{
			get
			{
				if (_default == null)
					_default = new NavigationHelper();
				return _default;
			}
		}

		#region Properties
		public static string GetRegion(DependencyObject obj)
		{
			return (string)obj.GetValue(RegionProperty);
		}

		public static void SetRegion(DependencyObject obj, string value)
		{
			obj.SetValue(RegionProperty, value);
		}

		public static readonly DependencyProperty RegionProperty =
			DependencyProperty.RegisterAttached("Region", typeof(string), typeof(NavigationHelper), new PropertyMetadata(String.Empty, RegionPropertyChanged));
		#endregion

		static Dictionary<string, ContentControl> regionDictionary = new Dictionary<string, ContentControl>();
		Stack<NavigationItem> NavigationStack = new Stack<NavigationItem>();

		private static void RegionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			if (DesignerProperties.GetIsInDesignMode(sender)) return;

			ContentControl regionControl = sender as ContentControl;
			var regionName = GetRegion(regionControl);

			if (!regionDictionary.ContainsKey(regionName))
			{
				regionDictionary.Add(regionName, regionControl);
				// Register the first view into the NavigationStack
				//regionControl.Loaded += (s, e) =>
				//    {
				//        var content = regionControl.Content as Control;
				//    };
			}
			else
				regionDictionary[regionName] = regionControl;

		}

		class NavigationItem
		{
			public string Region { get; set; }
			public string NavigationKey { get; set; }
			public object Data { get; set; }
		}

		public bool TryNavigateBack(int steps = 1)
		{
			if (NavigationStack.Count < 2) return false;

			if (steps < 1 || steps > (NavigationStack.Count + 1))
				throw new ArgumentException("Steps not valid: " + steps.ToString());

			Stack<NavigationItem> BackStack = new Stack<NavigationItem>();

			for (int i = 0; i < steps; i++)
			{
				BackStack.Push(NavigationStack.Pop());
			}

			var back = NavigationStack.Pop();

			bool NavSucceeded = TryNavigate(back.Region, back.NavigationKey, back.Data);

			if (NavSucceeded)
				return true;
			else
			{
				NavigationStack.Push(back);
				for (int i = 0; i < steps; i++)
				{
					NavigationStack.Push(BackStack.Pop());
				}
				return false;
			}

		}

		public bool TryNavigate(string region, string navigationKey, object data = null)
		{
			if (!regionDictionary.ContainsKey(region))
				throw new ArgumentException(region + " can't be found");

			var currentContent = regionDictionary[region].Content as Control;

			if (currentContent != null)
			{
				var navigationFromAwareVM = currentContent.DataContext as INavigationFromAware;
				if (navigationFromAwareVM != null)
					return navigationFromAwareVM.NavigatingFrom(data);
			}

			Control view = GetControlByNavigationKey(navigationKey);

			regionDictionary[region].Content = view;

			if (view.Resources.Contains("Storyboard1"))
			{
				var animation = view.Resources["Storyboard1"] as Storyboard;

				//animation.Begin();
			}

			var toNavigationAwareVM = view.DataContext as INavigationAware;
			if (toNavigationAwareVM != null)
				toNavigationAwareVM.NavigatingTo(data);

			NavigationStack.Push(new NavigationItem { Region = region, NavigationKey = navigationKey, Data = data });

			return true;

		}

		private Control GetControlByNavigationKey(string NavigationKey)
		{
			var localAssemblies = assemblies;

			foreach (var assembly in localAssemblies)
			{
				Debug.WriteLine("Assembly: " + assembly.FullName);
				var types = assembly.GetTypes();

				foreach (var type in types)
				{
					var atts = type.GetCustomAttributes(typeof(NavigationKeyAttribute), false);

					if (atts.Length != 0)
					{
						var navAttribute = atts[0] as NavigationKeyAttribute;

						if (navAttribute.Key == NavigationKey ||
							(type.Name == NavigationKey && navAttribute.Key == String.Empty))
						{
							var ctrl = Activator.CreateInstance(type);

							if (ctrl is Control)
							{
								return ctrl as Control;
							}
							else
								throw new ArgumentException(type.ToString() + " is not a derived from Control");
						}
					}

				}
			}

			throw new Exception(NavigationKey + " not found in all loaded assemblies");


		}

		List<Assembly> assemblies = new List<Assembly>();

		public void RegisterAssembly(object objectFromAssembly)
		{
			Assembly assembly = objectFromAssembly.GetType().Assembly;

			if (!assemblies.Contains(assembly))
				assemblies.Add(assembly);
		}

		private static IEnumerable<Assembly> GetLocalAssemblies()
		{
			//var localAssemblies =
			//             Deployment.Current.Parts.ToList()
			//             .Select(ap => Application.GetResourceStream(new Uri(ap.Source, UriKind.Relative)))
			//             .Select(sri => new AssemblyPart().Load(sri.Stream));
			//return localAssemblies;

			throw new Exception();
		}
	}

	public interface INavigationHelper
	{

		bool TryNavigate(string region, string navigationKey, object data = null);
		bool TryNavigateBack(int steps = 1);

		void RegisterAssembly(object objectFromAssembly);

	}

	public class NavigationKeyAttribute : Attribute
	{
		public string Key { get; set; }
		public NavigationKeyAttribute(string Key)
		{
			this.Key = Key;
		}
		/// <summary>
		/// Empty constructor => NavigationKey = Name of class.
		/// </summary>
		public NavigationKeyAttribute()
		{
			Key = String.Empty;
		}
	}

	public interface INavigationAware
	{
		void NavigatingTo(object data);
	}

	public interface INavigationFromAware
	{
		bool NavigatingFrom(object data);
	}


	public class NavigationBehavior : Behavior<ButtonBase>
	{
		public string NavigationKey { get; set; }
		public string Region { get; set; }


		protected override void OnAttached()
		{
			this.AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
		}

		void AssociatedObject_Click(object sender, RoutedEventArgs e)
		{
			NavigationHelper.Default.TryNavigate(Region, NavigationKey);
		}
	}
}
